Editable controls in smarttasks
About components
To fully enable the functionality of editing the list of documents and/or actors (or any other dynamic list related to a task) in a smart task, the controls must be used together. In the view file (HTML) of the smart task, follow these steps:
- Add the
wzp:rollbackselectorcontrol for each list that must be editable. - Add one
wzp:rollbackselector-panelcontrol for managing general save and cancel actions for these controls. - Optionally, use a specific expand-panel construction for collapsing/expanding
wzp:rollbackselector. (See Configure expanding/collapsing a wzp:rollbackselector section). - In the controller file (JS) of the smart task implementation, add the module
wzp.controlsto the list of modules in the smart task controller:
angular.module('wzput', ['ngResource', 'ngProgressLite', 'ngUtilities', 'localize', 'wzp.filters', 'settings', 'ui.help', 'wzp.controls', 'ui.actions', 'ui.action', 'ui.usertask', 'ui.comment', 'ui.label', 'ui.link', 'ngProgressLite', 'ui.helpers', 'ui.repeatview');
The wzp.rollbackselector control
This control allows showing items from a dynamic collection related to a smart task and changing them (add, delete and reorder), and after that saving or canceling these changes with or without completing the current smart task.
Data context
Each wzp:rollbackselector control needs two DataContextDefinition to get access to dynamic data. One for preselected values, and one for possible choices. The collection can return different sets of properties, but it should be converted to the same item's collections by the converters of the control.
Examples
To edit a list of documents, these two data contexts can be used.
- For a document already selected for the current smart task:
<DataContextDefinition>
<Name>AttachedDocuments</Name>
<Query>WzpUserTaskInserts?$filter=TaskId eq '{0}' and Attach eq true&$expand=Records&$select=RecordId,Records/Title,Records/State_Value,Records/RecordType_Value,Records/DocumentType_Value,Records/Summary&$orderby=Priority</Query>
<MaxOfflinePages>3</MaxOfflinePages>
<Parameters>
<Parameter>TaskId</Parameter>
</Parameters>
</DataContextDefinition>
- For a document that can be added to the smart task:
<DataContextDefinition>
<Name>AnswerDocuments</Name> <Query>Records?$select=ID,Summary,DocumentType_Value,State_Value&$orderby=ID,Summary&$filter=FileKey_Value eq '{0}' and State_Value ne 'UP' and ExternalDocId ne '' </Query>
<MaxOfflinePages>10</MaxOfflinePages>
<Parameters>
<Parameter>RegisterKey</Parameter>
</Parameters>
</DataContextDefinition>
To edit a list of documents, these two data contexts can be used.
- For representing actors for all active smart task for the current process:
<DataContextDefinition>
<Name>ActiveActors</Name> <Query>WzpUserTasks?$expand=NameKey&$select=InstanceId,NameKey_Value,TaskState_Value,NameKey/ID,NameKey/Summary,NameKey/NameType_Value,NameKey/NameCode&$filter=InstanceId eq '{0}' and (TaskState_Value eq 'OPEN' or TaskState_Value eq 'PENDING')&$orderby=TaskOrder</Query>
<MaxOfflinePages>10</MaxOfflinePages>
<Parameters>
<Parameter>InstanceId</Parameter>
</Parameters>
</DataContextDefinition>
- For actors that can be added the smart task:
<DataContextDefinition>
<Name>ForwardActors</Name> <Query>WzpFileUserRights?$select=ID,Summary,NameType_Value,NameCode_Value&$expand=NameKey,NameKey/AddressKey&$orderby=Summary&$filter=FileKey eq '{0}' and NameKey/AddressKey/Email ne ''</Query>
<MaxOfflinePages>10</MaxOfflinePages>
<Parameters>
<Parameter>RegisterKey</Parameter>
</Parameters>
</DataContextDefinition>
Add filters in the wzp.controls
You can enable predefined filtering options for the wzp.selector control in the init form or the wzp.rollbackselector control in the smarttask form.
Filtering options for the Init form
To add a filter control in the wzp.selector control in the Init form, follow these steps:
- Add or modify the
wzp.selectorcontrol (“parent” selector): - Change the
ng-controllervalue to"wzpSelectODATAWithFilterCtrl". - Add the css class
“wzp-select-with-filter”for correct styling. - Add the attribute
wzp-ad-selector-change-labelwith the following expression: - Add the attribute
wzp-ad-selector-filter-variable. - Add the
wzp:filter-selectorcontrol inside the parent selector with these parameters:
{{<twoWayBindings>.<DocumentLabelWithFilterValue>}}",
where the variable “twoWayBindings.DocumentLabelWithFilterValue” is equal to the "change-title-variable" attribute in the nested wzp:filter-selector control.
This should contain a variable equal to the "ng-model" attribute in the nested wzp:filter-selector control.
| Attribute | Description | Example or comment |
|---|---|---|
| ng-model | A pointer to the source model property, which this control is bound to. It should be equal to the 'wzp-ad-selector-filter-variable' attribute of the parent wzp.control and contains '.'. |
|
| ng-controller | Name of controller |
|
| predefined-filter-register | Register of parent selector |
|
| change-title-variable | A pointer to the 2-way binding variable for updating the parent label. It should be equal to the expression in the 'wzp-ad-selector-change-label' attribute of the parent wzp.control. |
|
- Define the
<twoWayBindings>object in scope of theui.startprocesscontroller of the Init form.
$scope.twoWayBindings = {};
This is an example of a wzp.control with a filter control for documents:
<wzp:selector
ng-model="dataSource.Documents"
ng-controller="wzpSelectODATAWithFilterCtrl"
labelgroup="ADV_SHARED"
label="DOCUMENTS"
placeholder="SELECT_DOCUMENTS"
options="{
register: 'Records',
filter: searchInCurrentCase(),
freetextfield:'Summary',
openItem:{icon:'content',title:'metadata',actionRegister:'Record'} }"
class="newline wzp-select-with-filter"
ng-disabled="formisdisabled"
wzp-ad-selector-change-label="{{twoWayBindings.DocumentLabelWithFilterValue}}"
wzp-ad-selector-filter-variable="twoWayBindings.DocumentFilters">
<wzp:filter-selector
ng-controller="wzpInitFormFilterForSelectorCtrl"
ng-model="twoWayBindings.DocumentFilters"
predefined-filter-register="Record"
change-title-variable="twoWayBindings.DocumentLabelWithFilterValue"
class="newline">
</wzp:filter-selector>
</wzp:selector>
Filtering options for the smarttask form
To enable filtering options in the smarttask form, you need to perform steps similar to the steps for the wzp.selector for the Init form but with the following differences:
- For the
wzp.rollbackselectorcontrol (“parent” selector control instead ofwzp.selector): - Use the
ng-controllerfor thewzp.rollbackcontrol. No specialng-controlleris necessary. - Add
query: getQueryWithFilterin theoptionsattribute. - For the
wzp:filter-selectorcontrol inside the parent selector, define the following additional parameters: ng-controller="wzpSmartTaskFilterForSelectorCtrl".ng-hide="noCapability('execute','online')"to hide the filter control in offline/read-only mode.
wzp.rollbackselector with a filter control for documents:
<wzp:rollbackselector
ng-model="Documents"
ng-controller="wzpCustomEditDocumentController"
default-data-context-name="AttachedDocuments"
item-convertor-name="converterFromOdataToSelectorForAttachments"
save-result-convertor-name="updateDocumentsFromAttachmentConverter"
options="{
register: 'Records',
openItem:{ actionRegister:'Record'},
datacontextName: 'DocumentsContext',
query: getQueryWithFilter
}"
readonly="noCapability('execute','online')"
labelgroup="CONTROL"
label="DOCUMENTS"
placeholder="SELECT_DOCUMENTS"
class="newline wzp-task-documentlist wzp-select-with-filter"
wzp-ad-selector-change-label="{{twoWayBindings.DocumentLabelWithFilterValue}}"
wzp-ad-selector-filter-variable="twoWayBindings.DocumentFilters"
>
<wzp:filter-selector
ng-controller="wzpSmartTaskFilterForSelectorCtrl"
ng-hide="noCapability('execute','online')"
ng-model="twoWayBindings.DocumentFilters"
predefined-filter-register="Record"
change-title-variable="twoWayBindings.DocumentLabelWithFilterValue"
class="newline">
</wzp:filter-selector>
</wzp:rollbackselector>
Control specification
Control specification should be placed in div with css style class definition. The default css class is wzp-task-editdocument.
Examples
- Editing documents
<div class="wzp-task-editdocument">
<wzp:rollbackselector
ng-model="Docs"
ng-controller="wzpCustomEditDocumentController"
default-data-context-name="AttachedDocuments"
item-convertor-name="converterFromOdataToSelectorForAttachments"
save-result-convertor-name="updateDocumentsFromAttachmentConverter"
options="{ register: 'Records', openItem:{ actionRegister:'Record'}, datacontextName: 'AnswerDocuments'}"
readonly="noCapability('execute','online')"
labelgroup="CONTROL"
label="DOCUMENTS"
placeholder="SELECT_DOCUMENTS"
required
class="newline wzp-task-documentlist">
</wzp:rollbackselector>
</div>
- Editing actors
<div class="wzp-task-editdocument expand-panel-body" ng-hide="!actorEditVisible">
<wzp:rollbackselector
ng-model="Actors"
ng-controller="wzpCustomEditDocumentController"
default-data-context-name="ActiveActors"
item-convertor-name="converterFromOdataToSelectorForActors"
save-result-convertor-name="updateActorsFromSomethingConverter"
options="{showSelected:true, openItem:{icon:'metadata',title:'metadata' , actionRegister:'Contact'} , iconType:'contacts', datacontextName: 'ForwardActors'}"
readonly="noCapability('execute','online')"
labelgroup="CONTROL"
label="ACTORS"
placeholder="SELECT_PARTIES"
class="newline wzp-task-documentlist body-column">
</wzp:rollbackselector>
</div>
Controller and converter functions
The Basis package contains a custom controller for the wzp.rollbackselector control for editing the documents list and the actors list.
It contains two pairs of converters (one for documents and one for actors):
- A converter for presenting information from
DataContextDefinitionwhich has a format that is suitable for thewzp.rollbackselectorcontrol (the names of the converter functions are used in the attributeitem-convertor-nameof thewzp.rollbackselector). - A converter for saving changes (the names of the converter functions are used in the attribute
save-result-convertor-nameof thewzp.rollbackselector).
Also, the controller should have an init part, which initializes converters and overwrites the openSelectedItemHandler delegate.
function init() {
$scope.$$childTail.itemConvertor = $scope[$scope.$$childTail.itemConvertorName];
$scope.$$childTail.openSelectedItemHandler = documentOpenSelectItemHandler;
$scope.$$childTail.saveResultConvertor = $scope[$scope.$$childTail.saveResultConvertorName];
};
Сontroller structure
angular.module('wzp.rollbackselector').controller('CustomEditDocumentController',['$scope',function ($scope)
{
/* Converters for Document*/
$scope.converterFromOdataToSelectorForAttachments = function (data, options, helpers) {};
$scope.updateDocumentsFromAttachmentConverter = function (data, result, hasChangesDelegate) { };
/* Converters for Actors*/
$scope.converterFromOdataToSelectorForActors = function (data, options, helpers) {}
$scope.updateActorsFromSomethingConverter = function (data, result, hasChangesDelegate) {};
/* overwrite openSelectedItemHandler delegate */
function documentOpenSelectItemHandler(itemData, handlerName) {
if (itemData.actionregister === 'Record') {
if (!itemData.text && !itemData.type)
return;
} $scope.$$childTail.baseOpenSelectedItemHandler(itemData, handlerName);
};
/* Function to init wzp.rollbackselector with convectors. Mandatory for custom controllers. */
function init() { };
init();
}]);
Examples of an item converter
These converters convert items from the data parameter to an array of objects using control options (options parameter) and some static methods (helpers parameter).
The structure of the data object depends on the ODATA request's result, defined by the corresponding DataContextDefinition.
A returned array of items should have the following properties: id, text, register, icon, type, namecode, actionregister.
If wzp.rollbackselector has any customizations, then the properties of returned items should be aligned with them.
Example of item converter for editing documents
$scope.converterFromOdataToSelectorForAttachments = function (data, options, helpers) {
var documents = [];
$.each(data, function (key, value) {
var recordProperty = { DocumentType_Value: '', State_Value: '' };
if (!!value.Records && value.Records.length > 0) {
recordProperty = value.Records[0];
}
var document = {
id: value.RecordId,
text: recordProperty.Summary,
register: options.register,
icon: helpers._getIcon(options, { DocumentType_Value: recordProperty.DocumentType_Value, State_Value: recordProperty.State_Value }),
type: (options.register == 'Records' ? recordProperty.DocumentType_Value : ''),
namecode: (options.register == 'Contacts' ? recordProperty.NameCode : ''),
actionregister: (!!options.openItem && !!options.openItem.actionRegister) ? options.openItem.actionRegister : options.register
}
helpers._protectedDocumentTitleFix(document);
documents.push(document);
});
return documents;
};
Example of item converter for editing actors
$scope.converterFromOdataToSelectorForActors = function (data, options, helpers) {
var actors = [];
$.each(data, function (key, value) {
var actorPropery = value.NameKey;
var actor =
{
id: actorPropery.ID,
text: actorPropery.Summary,
register: options.register,
icon: helpers._getIcon(options, actorPropery),
type: '',
namecode: actorPropery.NameCode,
actionregister: (!!options.openItem && !!options.openItem.actionRegister) ? options.openItem.actionRegister : options.register
};
actors.push(actor);
});
return actors;
}
Example of a result converter for editing documents
$scope.updateDocumentsFromAttachmentConverter = function (data, result, hasChangesDelegate) {
if (!checkforChanges(hasChangesDelegate)) return result;
var documents = [];
$.each(data, function (index, value) {
var document = {
$type: "Scanjour.OData.Client.Lite.WorkZone.Record, Scanjour.OData.Client.Lite", TypeName: "Som.Record", MediaResource: null, ID: value.id
}
documents.push(document);
})
var _propertyToUpdateName = 'Documents';
result.Attachments = [_propertyToUpdateName];
result.Properties[_propertyToUpdateName].value = documents;
return result;
};
Where :
- data – changed collection provided by the control.
- Result – the populated template response part.
- hasChangesDelegate – delegate to define if any changes were done. If no changes were done, then there is no need to save anything (but there can be changes here).
For a description of response populating, see Populating document changes.
Example of a result converter for editing actors
$scope.updateActorsFromSomethingConverter = function (data, result, hasChangesDelegate) {
if (!checkforChanges(hasChangesDelegate)) return result;
var actors = [];
$.each(data, function (index, value) {
var actor = {
$type: "Scanjour.OData.Client.Lite.WorkZone.Contact, Scanjour.OData.Client.Lite", TypeName: "Som.Contact", MediaResource: null, ID: value.id
};
actors.push(actor);
});
result.Actors = actors;
return result
};
For a description of the parameters, see Example of item converter for editing documents.
For explanation about response populating, see Populating actor changes.
Example of result converter for editing actors
$scope.updateActorsFromSomethingConverter = function (data, result, hasChangesDelegate) {
if (!checkforChanges(hasChangesDelegate)) return result;
var actors = [];
$.each(data, function (index, value) {
var actor = {
$type: "Scanjour.OData.Client.Lite.WorkZone.Contact, Scanjour.OData.Client.Lite", TypeName: "Som.Contact", MediaResource: null, ID: value.id
};
actors.push(actor);
});
result.Actors = actors;
return result
};
For a description of the parameters, see Example of item converter for editing documents.
For explanation about response populating, see Populating actor changes.
Wzp:rollbackselector-panel control
This control presents a separated panel with a save and a cancel button which give the ability to save or cancel changes done with all wzp:rollbackselector controls at the same time.
The panel is invisible by default, and becomes visible when any changes are done by any wzp:rollbackselector.
The Cancel button cancels changes in all wzp:rollbackselector controls.
The Save button saves changes in all wzp:rollbackselector controls in one update request.
Control specification
The control should be placed in div with a css style class definition. The default css class is edit-panel.
The attribute ng-hide="noCapability('execute','online')" makes the panel visible only when a smart task has both ‘execute’ and ‘online’ capability at the same time.
Example
<div class="edit-panel" ng-hide="noCapability('execute','online')">
<wzp:rollbackselector-panel
readonly="noCapability('execute','online')"
labelgroup="CONTROL"
cancel-button-label="CANCEL"
cancel-button-hide="false"
save-button-label="SAVE"
capability="execute"
usertask-response-template-name="Update"
class="newline">
</wzp:rollbackselector-panel>
</div>
Configure expanding/collapsing a wzp:rollbackselector section
Using this html structure and styles, and some javascript+ angular code, a wzp:rollbackselector can be placed in expand-colapse panel.
HTML
<div class="expand-panel-header" ng-hide="actorEditVisible">
<div class="button-column" ng-click="actorEditSwitch()">
<div></div>
</div>
<div class="wzp-control-labeled body-column">
<label>{{$root.i18n('CONTROL', 'ACTORS')}}</label>
</div>
</div>
<div class="wzp-task-editdocument expand-panel-body" ng-hide="!actorEditVisible">
<div class="button-column" ng-click="actorEditSwitch()">
<div></div>
</div>
<wzp:rollbackselector
* * *
labelgroup="CONTROL"
label="ACTORS">
</wzp:rollbackselector>
</div>
Where
{{$root.i18n('CONTROL', 'ACTORS')}} should be equal to the label group and label attribute of the wzp:rollbackselector control.
JavaScript + Angular code
You can add new properties in the smart task controller (in angular.module('ui.usertask').controller('ApproveTaskCtr',) body for switching between expanded and collapsed states of the section:
$scope.actorEditVisible = false;
$scope.actorEditSwitch = function () {
$scope.actorEditVisible = !$scope.actorEditVisible;
}
Response Template population
Response structure
You can save any changes in document or actor collections (or any other changes for smart tasks) by performing an “Update” process action for smart tasks with a correctly populated ResponseTemplate.
For this action you must use the ResponceTemplate with the name “Update” form SmartTask Metadata ResponseTemplates .
The current structure of this ResponseTemplate is (in JSON format) as follows:
{
"$type": "Scanjour.Workflow4.Base.UserTaskUpdateResponse, Scanjour.Workflow4.Base, Version=4.1.0.0, Culture=neutral, PublicKeyToken=null",
"NearDueDate": "\/Date(-62135596800000)\/",
"DueDate": "\/Date(-62135596800000)\/",
"Attachments": null,
"Actors": null,
"OptionalActors": null,
"Action": null,
"Comment": null,
"Properties": {
"$type": "Scanjour.Workflow4.Base.UserTaskProperties, Scanjour.Workflow4.Base, Version=4.1.0.0, Culture=neutral, PublicKeyToken=null",
"Documents": {
"$type": "Scanjour.Workflow4.Base.UserTaskProperty, Scanjour.Workflow4.Base, Version=4.1.0.0, Culture=neutral, PublicKeyToken=null",
"key": "Documents",
"type": "Scanjour.OData.Client.Lite.WorkZone.Record[]",
"value": [
{
"$type": "Scanjour.OData.Client.Lite.WorkZone.Record, Scanjour.OData.Client.Lite, Version=4.1.0.0, Culture=neutral, PublicKeyToken=null",
"TypeName": "Som.Record",
"MediaResource": null,
"ID": "7",
"Actions": [ ],
"Properties": [ { "$type": "Scanjour.OData.Client.Lite.PropertyMember, Scanjour.OData.Client.Lite, Version=4.1.0.0, Culture=neutral, PublicKeyToken=null", "key": "ID", "type": "System.String", "value": "7" } ],
"SubEntries": [ ],
"Feeds": [ ]
}
]
},
"FileNo": {},
"Officer": {},
"OfficerName": {},
"Register": {},
"RegisterKey": { },
"InstanceId": { },
"TaskId": { }
},
"Answers": null,
"Identity": null
}
Populating actor changes
To save changes in the actor list, you must save the new collection of changed items in the Actors (or OptionalActors) property of the responseTemplate object.
These collections have the type Som.Contact[], so an element of an array must be in the following format (in JSON):
{
$type: "Scanjour.OData.Client.Lite.WorkZone.Contact, Scanjour.OData.Client.Lite", TypeName: "Som.Contact",
MediaResource: null,
ID:id
};
Where id is the ID of a contact from the wzp:rollbackselector items.
Populating document changes
To save changes in the document list, you must save the new collection of changed items using this sequence of actions:
- Save the name of the collection with changed values from the Properties collections as an array of a string:
- From an array with new items of the type
Som.Recordin the following format, enter: - Save this array in Properties[“
Documents”].value.
Attachments = [“Documents”];
{
$type: "Scanjour.OData.Client.Lite.WorkZone.Record, Scanjour.OData.Client.Lite", TypeName: "Som.Record",
MediaResource: null,
ID: id
},
Where id is the ID of Record from wzp:rollbackselector items.
Add a filter control in The <wzp-multi-selector> and <wzp:rollbackselector> parent controls
To add a filter control in the <wzp-multi-selector> and <wzp:rollbackselector> parent controls, follow these steps:
- Add or modify the
<wzp-multi-selector>and<wzp:rollbackselector>controls (“parent” selector):- Change the
ng-controllervalue to "wzpSelectODATAWithFilterCtrl". - Add the css class “
wzp-select-with-filter” for correct styling. - Add the attribute
wzp-ad-selector-filter-variable, that should contain a variable equal to the "ng-model" attribute in the nested<wzp:document-selector-filter>control.
- Change the
- Add the
<wzp:document-selector-filter>control inside the parent selector with "ng-model" attribute equal to the 'wzp-ad-selector-filter-variable' attribute of the parent<wzp-multi-selector>and<wzp:rollbackselector>and contains '.'. -
Define the
<twoWayBindings>object in scope of the controller of the form.$scope.twoWayBindings = {};